package moe.service;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import moe.dao.BaseDao;
import moe.dao.PoolDao;
import moe.dao.PostDao;
import moe.entity.Pool;
import moe.entity.Post;
import moe.util.JSONBuilder;
import moe.util.Paging;
import moe.util.UriMappedUtil;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;

public class PoolHandler extends AbstractHandler {
	private static final Logger log = Logger.getLogger(PostHandler.class.getName());

	private PoolDao poolDao;
	private PostDao postDao;

	public PoolHandler() {
		postDao = new PostDao(BaseDao.ConnType.DEFAULT);
		poolDao = new PoolDao();
	}

	public void listPools(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		Paging paging = createPage(query);

		long time = System.currentTimeMillis();
		List<Pool> pools = poolDao.listPools(paging);
		fetchThumbPosts(pools);
		if (log.isLoggable(Level.FINE)) {
			log.fine("Query(listPools) time: " + (System.currentTimeMillis() - time) + "ms");
		}
		for (Pool pool : pools) {
			clearDescription(pool);
		}
		String json = createPoolsListJson(pools, paging, query);
		sendJsonResponse(response, json);
	}

	public void listPoolsByName(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		if (!query.containsKey("name")) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
		String name = query.get("name")[0];
		Paging paging = createPage(query);

		long time = System.currentTimeMillis();
		List<Pool> pools = poolDao.listPoolsByName(name, paging);
		fetchThumbPosts(pools);
		if (log.isLoggable(Level.FINE)) {
			log.fine("Query(listPoolsByName) time: " + (System.currentTimeMillis() - time) + "ms");
		}
		for (Pool pool : pools) {
			clearDescription(pool);
		}
		String json = createPoolsListJson(pools, paging, query);
		sendJsonResponse(response, json);
	}

	@SuppressWarnings("unchecked")
	public void listPoolPosts(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		if (!query.containsKey("id")) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
		long id = Long.valueOf(query.get("id")[0]);
		Paging paging = createPage(query);

		long time = System.currentTimeMillis();
		Pool pool = poolDao.getPoolById(id, paging);
		List<Post> posts = Collections.EMPTY_LIST;
		if (pool != null) {
			List<Long> ids = new ArrayList<Long>(pool.getPosts().size());
			for (Post post : pool.getPosts()) {
				ids.add(post.id);
			}
			if (ids.size() > 0) {
				posts = postDao.getPosts(ids.toArray(new Long[ids.size()]));
				UriMappedUtil.resolveUrl(posts);
			}
		}
		if (log.isLoggable(Level.FINE)) {
			log.fine("Query(listPoolPosts) time: " + (System.currentTimeMillis() - time) + "ms");
		}
		clearDescription(pool);

		JSONBuilder builder =
				JSONBuilder.build().callback(getCallback(query)).add("posts", posts, false).add(
						"pool",
						pool,
						false);
		bindPaging(builder, paging);
		String json = builder.buildString();

		sendJsonResponse(response, json);
		saveToSession(context, query, posts);
	}

	public void searchPool(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);
		if (!query.containsKey("pool")) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
		if (query.containsKey("collect")) {
			collectPoolPosts(context, query);
			return;
		}
		String action = query.get("pool")[0];
		String[] params = action.split(" ");
		if (params.length < 2) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}
		if (params[0].equals("id")) {
			query.put("id", new String[] { params[1] });
			listPoolPosts(context, query);
		} else if (params[0].equals("name")) {
			query.put("name", new String[] { action.substring(5) });
			listPoolsByName(context, query);
		} else {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
		}
	}

	private void collectPoolPosts(HttpContext context, Map<String, String[]> query) {
		HttpResponse response = getHttpResponse(context);

		String action = query.get("pool")[0];
		String[] params = action.split(" ");
		if (params.length < 2) {
			response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
			return;
		}

		if (params[0].equals("id")) {
			query.put("id", new String[] { params[1] });
			doCollectPoolPosts(context, query);
		} else {
			HttpEntity entity = null;
			try {
				entity =
						new StringEntity(
								"Collection failure, cause: Without the support of parameters - "
										+ params[0],
								"text/plain",
								"UTF-8");
			} catch (UnsupportedEncodingException e) {
				response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
				return;
			}
			response.setStatusCode(HttpStatus.SC_OK);
			response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
			response.setEntity(entity);
		}
	}

	private void doCollectPoolPosts(HttpContext context, Map<String, String[]> query) {
		final long id = Long.parseLong(query.get("id")[0]);

		final HttpResponse response = getHttpResponse(context);
		final HttpRequest request =
				(HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);

		final String collectType = query.get("collect")[0];
		final StringBuilder fileListBuilder = new StringBuilder();
		final String[] errorMessage = new String[1];
		final List<Long> collectFailedIds = new LinkedList<Long>();

		final BlockingQueue<Post> postQueue = new LinkedBlockingQueue<Post>();
		final Thread[] collectThreads = new Thread[2];
		collectThreads[0] = new Thread("PoolPostsSearchThread") {
			@Override
			public void run() {
				Thread collectThread = collectThreads[1];
				int pageSize = 100, next = 1;
				Paging paging = new Paging(pageSize);
				List<Post> posts = null;
				do {
					paging.setPageNumber(next);
					try {
						Pool pool = poolDao.getPoolById(id, paging);
						if (pool == null) {
							break;
						}
						List<Long> ids = new ArrayList<Long>(pool.getPosts().size());
						for (Post post : pool.getPosts()) {
							ids.add(post.id);
						}
						if (ids.size() > 0) {
							posts = postDao.getPosts(ids.toArray(new Long[ids.size()]));
							UriMappedUtil.resolveUrl(posts);
						}
					} catch (Throwable t) {
						errorMessage[0] = t.getMessage();
						return;
					}
					for (Post post : posts) {
						boolean offer = false;
						do {
							offer = false;
							try {
								offer = postQueue.offer(post, 5, TimeUnit.SECONDS);
							} catch (InterruptedException e) {
								// ignore
							}
							if (!collectThread.isAlive()) {
								return;
							}
						} while (!offer);
					}
					if (paging.hasNext()) {
						next = paging.getNext();
					}
				} while (paging.hasNext());
			}
		};
		collectThreads[1] = new Thread("PoolPostsCollectThread") {
			private PostHandler postHandler = new PostHandler();

			@Override
			public void run() {
				Thread searchThread = collectThreads[0];
				Post post = null;
				while (searchThread.isAlive() || !postQueue.isEmpty()) {
					post = null;
					do {
						try {
							post = postQueue.poll(5, TimeUnit.SECONDS);
						} catch (InterruptedException e) {
							// ignore
						}
						if (post == null && !searchThread.isAlive()) {
							return;
						}
					} while (post == null);
					// do collect
					if (collectType.equals("list")) {
						fileListBuilder.append("http://")
								.append(request.getFirstHeader("Host").getValue())
								.append(post.fileUrl)
								.append("\r\n");
					} else if (collectType.equals("directory")) {
						try {
							postHandler.collectPost(post, response);
						} catch (Throwable t) {
							collectFailedIds.add(post.id);
						}
					}
				}
			}
		};
		for (Thread thread : collectThreads) {
			thread.setDaemon(true);
			thread.start();
		}

		try {
			for (Thread thread : collectThreads) {
				thread.join();
			}
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
		HttpEntity entity = null;
		try {
			if (errorMessage[0] != null) {
				entity =
						new StringEntity(
								"Collection failure, cause:" + errorMessage[0],
								"text/plain",
								"UTF-8");
			} else if (!collectFailedIds.isEmpty()) {
				StringBuilder msg = new StringBuilder("The following post collection failure:");
				for (int i = 0, length = collectFailedIds.size() - 1; i < length; i++) {
					msg.append(collectFailedIds.get(i)).append(", ");
				}
				msg.append(collectFailedIds.get(collectFailedIds.size() - 1));
				entity = new StringEntity(msg.toString(), "text/plain", "UTF-8");
			} else if (collectType.equals("list")) {
				entity =
						new StringEntity(
								fileListBuilder.toString(),
								"application/octet-stream",
								"UTF-8");
				response.addHeader("Content-Disposition", "attachment; filename=download_list.lst");
			} else if (collectType.equals("directory")) {
				entity = new StringEntity("success", "text/plain", "UTF-8");
			}
		} catch (UnsupportedEncodingException e) {
			response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
			return;
		}
		response.setStatusCode(HttpStatus.SC_OK);
		response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
		response.setEntity(entity);
	}

	@SuppressWarnings("unchecked")
	private void fetchThumbPosts(List<Pool> pools) {
		if (pools.size() < 1) {
			return;
		}
		List<Long> ids = new ArrayList<Long>(pools.size());
		for (Pool pool : pools) {
			ids.add(pool.getPosts().get(0).id);
		}
		List<Post> posts = postDao.getPosts(ids.toArray(new Long[ids.size()]));
		UriMappedUtil.resolveUrl(posts);
		for (Pool pool : pools) {
			for (Post post : posts) {
				if (pool.getPosts().get(0).id.equals(post.id)) {
					pool.setPosts(Arrays.asList(post));
					break;
				}
			}
			if (pool.getPosts().get(0).md5 == null) {
				pool.setPosts(Collections.EMPTY_LIST);
			}
		}
	}

	private String createPoolsListJson(List<Pool> pools, Paging paging, Map<String, String[]> query) {
		JSONBuilder builder =
				JSONBuilder.build().callback(getCallback(query)).add("pools", pools, false);
		bindPaging(builder, paging);
		return builder.buildString();
	}

	private void clearDescription(Pool pool) {
		if (pool == null) {
			return;
		}
		String des = pool.getDescription().replaceAll("\\n", "<br/>").replaceAll("\\r", "");
		pool.setDescription(des);
	}
}
